//==============================================================================
//
//            DData - the data string class in the ofc-library
//
//              Copyright (C) 2003-2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-07-20 06:23:36 $ $Revision: 1.25 $
//
//==============================================================================

#include "ofc/DData.h"

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DDatable.h"
#include "ofc/DTextable.h"
#include "ofc/DTextScannable.h"
#include "ofc/DComparable.h"
#include "ofc/DSizeable.h"
#include "ofc/DDataReadable.h"
#include "ofc/DDataWritable.h"

#include "ofc/DText.h"


//
// The DData class implements a number of methods for manipulating data strings.
// @example
//
// #include <stdio.h>
// #include "ofc/DData.h"
// 
// int main(int argc, char *argv[])
// {
//   DData *dat1 = [DData alloc];
//   DData *dat2 = [DData new  ];
//   DText *str;
// 
//   [dat1 init :"DData stores data strings" :25]; // Init with a data string
// 
//   printf("Length of data string1:%ld\n", [dat1 length]);
// 
//   str = [dat1 tohexString];
//   printf("Hex string of data string:%s.\n", [str cstring]);
//   [str free];
// 
//   str = [dat1 toBase64];
//   printf("Base64 string of data string:%s.\n", [str cstring]);
//   [str free];
// 
//   printf("Hash value of data string:%ld.\n", [dat1 hash]);
// 
//   printf("Index of \"data\" in data string is:%ld.\n", [dat1 index :"data" :4 :0 :-1]);
// 
//   [dat2 set :"DData stores data strings" :1 :17]; // Set with substring
// 
//   [dat2 put :16 :'!'];                 // Put a character in the string
// 
//   [dat2 insert :16 :16 :" strings" :9]; // Insert a data string
// 
//   [dat2 delete :11 :15];               // Delete a substring
// 
//   str = [dat2 toText];
//   printf("Data string2:%s.\n", [str cstring]);
//   [str free];
// 
//   printf("Data string1 is ");          // Compare strings
//   if ([dat1 compare :dat2] > 0)
//     printf("greater");
//   else if ([dat1 compare :dat2] < 0)
//     printf("smaller");
//   else
//     printf("equal");
//   printf(" than data string2.\n");
// 
//   [dat1 free];                         // Cleanup
//   [dat2 free];
// 
//   return 0;
// }
//

@interface DData : DObject <DDatable,DTextable,DTextScannable,DComparable,DSizeable,DDataReadable,DDataWritable>
{
@private
  unsigned long  _size;     // the allocated size for the data
  unsigned       _extra;    // the extra size amount
  unsigned long  _length;   // the length of the data
  unsigned char *_data;     // the data
  unsigned long  _pntr;     // the 'file pointer' for the protocols
  int            _error;    // the last reported error (or 0)
}

#endif



@implementation DData


static long index2offset(DData *str, int index)
{
  if (index < 0)
    index = str->_length + index;

  if (index < 0)
  {
    index  = 0;

    WARNING(DW_INVALID_ARG, "index");
  }
  if (index > str->_length)
  {  
    index = str->_length-1;

    WARNING(DW_INVALID_ARG, "index");
  }

  return index;
}

//// Constructors

//
// Initialise to an empty data string
//
// @return the object
//

- (DData *) init
{
  [super init];
  
  _length = 0;
  _size   = 0;
  _extra  = 0;
  _data   = NULL;
  _pntr   = 0;
  _error  = 0;
    
  return self;
}

//
// Initialise the instance with data
//
// @param data     the data
// @param length   the length of the data
//
// @return the string object
//
- (DData *) init :(const unsigned char *) data :(unsigned long) length
{
  [self init];

  [self set :data :length];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the data string object
//
// @return the object
//
- shallowCopy
{
  DData *copy = [super shallowCopy];
  
  copy->_data = (unsigned char *) objc_malloc(sizeof(char) * _size);
  
  memcpy(copy->_data, _data, _length);
  
  return copy;
}


//// Deconstructor

//
// Free the object
//
// @return the object
//
- free
{
  if (_data != NULL)
  {
    objc_free(_data);
  }

  [super free];

  return self;
}


//// Size methods

//
// Insure the size for the data string
//
// @param size     the requested size for the data
//
// @return the object
//
- (DData *) size :(unsigned long) size
{
  if (size  > _size)
  {
    _size = size + _extra;
    
    if (_data == NULL)
    {
      _data = (unsigned char *) objc_malloc(sizeof(char) * _size);
    }
    else
    {
      _data = (unsigned char *) objc_realloc(_data, sizeof(char) * _size);
    }
  }

  return self;
}

//
// Set the amount of extra size increase if the size must be extended
//
// @param extra    the amount (def. 0)
//
// @return the object
//
- (DData *) extra :(unsigned) extra
{
  _extra = extra;

  return self;
}


//// Conversion methods

//
// Convert the data string to a hexidecimal text string (every
// character in the string is converted to two text characters
// hexadecimal)
//
// @return the (new) string object
//
- (DText *) tohexString
{
  DText *string = [DText new];

  [string size :(_length * 2)];

  if (_length > 0)
  {
    unsigned char *src;
    
    long  length = _length;
    char  upper;
    char  lower;
    
    src = _data;
    while (length > 0)
    {
      upper = ((*src) >> 4) & 15;
      lower =  (*src) & 15;
 
      [string push :(upper > 9) ? (upper + ('a' - 10)) : (upper + '0')];
      [string push :(lower > 9) ? (lower + ('a' - 10)) : (lower + '0')];

      src++;
      length--;
    }
  }
  
  return string;
}

//
// Calculate the hash of the data string
//
// @return the hash value
//
- (unsigned long) hash
{
  unsigned long  hash   = 0;
  unsigned char *src    = _data;
  long           length = _length;

  while (length-- > 0)
  {
    hash = (hash << 5) + hash + (*src++);
  }

  return hash;
}


#if _PRIVATE_

// ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
// 0123456789012345678901234567890123456789012345678901234567890123
//          1         2         3         4         5         6

char _toBase64(unsigned nr)
{
  if (nr <= 25)
  {
    return 'A' + nr;
  }
  if (nr <= 51)
  {
    return 'a' + (nr - 26);
  }
  if (nr <= 61)
  {
    return '0' + (nr - 52);
  }
  if (nr == 62)
  {
    return '+';
  }
  return '/';
}

BOOL _fromBase64(const char **ptr, unsigned *nr)
{
  char ch;
  BOOL ok   = YES;
  BOOL done;
  
  do
  {
    done = YES;
    ch = **ptr;
    
    if (ch == EOS)
    { 
      *nr = 0;
       ok = NO;
    }
    else 
    {
      if (ch == '=')
      {
        *nr = 0;
      
        ok = NO;
      }
      else if ((ch >= 'A') && (ch <= 'Z'))
      {
        *nr = (**ptr - 'A');
      }
      else if ((ch >= 'a') && (ch <= 'z'))
      {
        *nr = 26 + (ch - 'a');
      }
      else if ((ch >= '0') && (ch <= '9'))
      {
        *nr = 52 + (ch - '0');
      }
      else if (ch == '+')
      {
        *nr = 62;
      }
      else if (ch == '/')
      {
        *nr = 63;
      }
      else
      {
        done = NO;
      }
      
      (*ptr)++; // skip anything else
    }
  }
  while (!done);
  
  return ok;
}

#endif


//
// Convert to Base64
// 
// @return a (new) text string object (or nil)
// 
- (DText *) toBase64
{
  DText *text = nil;
  
  if (_length > 0)
  {
    long           len = _length;
    unsigned char *src = _data;
    
    text = [[DText new] size :(len / 3 + 1) * 4];
    
    while (len >= 3)
    {
      [text push :_toBase64(src[0] >> 2)];
      [text push :_toBase64(((src[0] << 4) & 0x30) | (src[1] >> 4))];
      [text push :_toBase64(((src[1] << 2) & 0x3C) | (src[2] >> 6))];
      [text push :_toBase64(src[2] & 0x3F)];
      
      len -= 3;
      src += 3;
    }
    
    if (len > 0)
    {
      [text push :_toBase64(src[0] >> 2)];
      
      if (len == 1)
      {
        [text push :_toBase64((src[0] << 4) & 0x30)];
        [text push :'='];
      }
      else // len == 2
      {
        [text push :_toBase64(((src[0] << 4) & 0x30) | (src[1] >> 4))];
        [text push :_toBase64((src[1] << 2) & 0x3C)];
      }
      [text push :'='];
    }
  }
  
  return text;
}

//
// Convert to a text string with printable characters
// (by changing non-printable characters to a dot character)
//
// @return a (new) text string object
//
- (DText *) toPrintable
{
  DText         *text = [DText new];
  unsigned char *src  = _data;
  long           len  = _length;
  char           ch;

  [text size :_length];

  while (len-- > 0)
  {
    ch = *src++;
    if (!isprint(ch))
    {
      ch = '.';
    }
    [text push :ch];
  }
  return text;
}


//// DTextable protocol methods

//
// Convert the object to a text string without any
// conversion. (see toPrintable for a method that 
// convert the data object to a true printable text object)
//
// @return a (new) text string object
//
- (DText *) toText
{
  DText *text = [DText new] ;

  [text size :_length];
  
  if (_length > 0)
  {
    [text set :(const char *) _data :0 :_length-1];
  }
  
  return text;
}


//// Data string setters

//
// Clear the data string
// 
// @return the object
// 
- (DData *) clear
{
  _length = 0;
  _pntr   = 0;
  
  return self;
}

//
// Set data in the data string
//
// @param data     the data for the string ('\0' allowed)
// @param length   the length of the data
//
// @return the object
//
- (DData *) set :(const unsigned char *) data :(unsigned long) length
{
  _length = 0;
  _pntr   = 0;
  
  if (data != NULL)
  {
    _length = length;
  }

  [self size :_length];

  memcpy(_data, data, _length);
  
  return self;
}

//
// Set a substring of data in the data string
//
// @param data     the data for the string ('\0' allowed)
// @param from     the start in the data
// @param to       the end in the data (incl)
//
// @return the object
//
- (DData *) set :(const unsigned char *) data :(long) from :(long) to
{
  if (from < 0) from = 0;
  if (to   < 0) to   = 0;

  if (to >= from)
  {
    _length = (data != NULL) ? to - from + 1 : 0;
    _pntr   = 0;
    
    [self size :_length];

    memcpy(_data, data + from, _length);
  }
  
  return self;
}

//
// Set the data string with Base64 converted data
// 
// @param cstring  the c-string with the base 64 codes
// 
// @return the object
// 
- (DData *) fromBase64 :(const char *) cstring
{
  long len = (cstring == NULL ? 0 : strlen(cstring));

  _length = 0;
  _pntr   = 0;
  
  if (len > 0)
  {
    BOOL ok   = YES;
    const char *src = cstring;
    
    [self size :(len / 4 + 1) * 3];
    
    while (ok)
    {
      unsigned ch1,ch2,ch3,ch4;
      BOOL     ok1,ok2,ok3,ok4;
      
      ok1 = _fromBase64(&src, &ch1);
      ok2 = _fromBase64(&src, &ch2);
      ok3 = _fromBase64(&src, &ch3);
      ok4 = _fromBase64(&src, &ch4);
      
      if (ok2)
        _data[_length++] = (ch1 << 2) | (ch2 >> 4);
      if (ok3)
        _data[_length++] = (ch2 << 4) | (ch3 >> 2);
      if (ok4)
      _data[_length++] = (ch3 << 6) | ch4;
      
      ok = ok4;
    }
  }
  return self;
}


//// Byte methods

//
// Put a byte at the given index
//
// @param index     the index (-length..+length)
// @param byte      the byte to be placed
//
// @return the object
//
- (DData *) put :(long) index :(unsigned char) byte
{
  if (_length > 0)
  {
    index = index2offset(self, index);

    _data[index] = byte;
  }
  
  return self;
}

//
// Get the byte at the given index
//
// @param index    the index 
//
// @return the byte from the data string
//
- (unsigned char) get :(long) index
{
  if (_length > 0)
  {
    index = index2offset(self, index);
  
    return _data[index];
  }
  else
    return EOS;
}

//
// Delete the byte at the given index
//
// @param index    the index
//
// @return the object
//
- (DData *) delete :(long) index
{
  index = index2offset(self, index);
  
  if (_length > 0)
  {
    memmove(_data+index, _data+index+1, _length-index-1);

    _length--;
  }
  
  return self;
}


//// Slice methods

//
// Insert a data string in a part of the data string
//
// @param from     the start index
// @param to       the end index (to >= (from-1))
// @param data     the new data
// @param length   the length of the new data
//
// @return the object
//
- (DData *) insert :(long) from :(long) to :(const unsigned char *) data :(unsigned long) length
{
  from = index2offset(self, from);
  to   = index2offset(self, to  );

  if (to >= (from-1))
  {
    length = (data != NULL) ? length : 0;

    [self size :(_length+length-(to-from+1))];
    
    memmove(_data+from+length, _data+to+1, (_length-to-1));
    
    memmove(_data+from, data, length);

    _length += (length-(to-from+1));
  }
    
  return self;
}

//
// Return a substring
//
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return a (new) data string object
//
- (DData *) get :(long) from :(long) to
{
  DData *sub = [DData new];
  
  from = index2offset(self, from);
  to   = index2offset(self, to  );

  if (to >= from)
  {
    [sub set :[self data] :from :to];
  }
  return nil;
}

//
// Delete a substring
//
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return the object
//
- (DData *) delete :(long) from :(long) to
{
  if (_length > 0)
  {
    from = index2offset(self, from);
    to   = index2offset(self, to  );

    if (to > from)
    {
      memmove(_data+from, _data+to+1, (_length-to-1));

      _length -= (to-from+1);
    }
  }
  return self;
}


//// Data string member methods

//
// Check if the data string is empty
//
// @return Is it empty ?
//
- (BOOL) isEmpty
{
  return (_length == 0);
}

//
// Return the length of the data string
//
// @return the length
//
- (unsigned long) length
{
  return _length;
}

//
// Return the pointer to the data of the string
//
// @return the pointer to the data
//
- (const unsigned char *) data
{
  return _data;
}

//
// Return the size of the data string in bytes
//
// @return the size
//
- (unsigned long) size
{
  return (sizeof(unsigned char) * _length);
}

//
// Return the last error
//
// @return the error (0, ENODATA)
//
- (int) error
{
  return _error;
}


//// DDatable protocol methods

//
// Convert the object to a data string object
//
// @return a (new) data string object
//
- (DData *) toData
{
  return [self copy];
}


//// DDataReadable protocol methods

//
// Read text string (not-print characters are replaced by a '.')
//
// @param length   the length of the text to be read
//
// @return a (new) text string object
//
- (DText *) readText :(unsigned long) length
{
  DText *text = [DText new];

  [text size :length];
  
  while ((_pntr < _length) && (length > 0))
  {
    [text push :_data[_pntr++]];

    length--;
  }
  
  return text;
}

//
// Read a data string 
//
// @param length   the length of the data
//
// @return a (new) data object (empty if no data available)
//
- (DData *) readData :(unsigned long) length;
{
  DData *dest = [DData new];
    
  [dest size :length];

  if (_pntr + length > _length)
    length = _length - _pntr;

  if (length > 0)
  {
    [dest set :_data :_pntr :_pntr + length];

    _pntr += length;
    
    _error = 0;
  }
  else
    _error = ENODATA;

  return dest;
}

//
// Read a data string 
//
// @param dest     the destination buffer
// @param length   the length of the data (and minimum size of dest)
//
// @return the actual number of bytes read
//
- (unsigned long) readData :(unsigned char *) dest :(unsigned long) length
{
  unsigned long result = 0;
  
  if (dest == NULL)
  {
    WARNING(DW_INVALID_ARG, "dest");
  }
  else
  {
    result = length;
    
    if (_pntr + result > _length)
      result = _length - _pntr;

    if (result > 0)
    {
      memmove(dest, _data+_pntr, result);

      _pntr += result;
      
      _error = 0;
    }
    else
      _error = ENODATA;
  }
  
  return result;
}

//
// Read a character
//
// @return the character read (or EOS)
//
- (char) readChar
{
  char ch = EOS;
  
  if (_pntr + sizeof(char)  <= _length)
  {
    ch = (char) _data[_pntr++];

    _error = 0;
  }
  else
    _error = ENODATA;

  return ch;
}

//
// Read a byte
//
// @return the byte read (or 0)
//
- (unsigned char) readByte
{
  unsigned char ch = 0;
  
  if (_pntr + sizeof(unsigned char) <= _length)
  {
    ch  = _data[_pntr++];

    _error = 0;
  }
  else
    _error = ENODATA;
    
  return ch;
}

//
// Read a short
//
// @return the short read (or 0)
//
- (short) readShort
{
  short sh = 0;
  
  if (_pntr + sizeof(short) <= _length)
  {
    sh = *(short *)(_data + _pntr);

    _pntr += sizeof(short);

    _error = 0;
  }
  else
    _error = ENODATA;
    
  return sh;
}

//
// Read a long
//
// @return the long read (or 0)
//
- (long) readLong
{
  long val = 0L;
  
  if (_pntr + sizeof(long) <= _length)
  {
    val = *(long *)(_data + _pntr);

    _pntr += sizeof(long);

    _error = 0;
  }
  else
    _error = ENODATA;
    
  return val;
}

//
// Read a double
//
// @return the double read (or 0.0)
//
- (double) readDouble
{
  double val = 0.0;
    
  if (_pntr + sizeof(double) <= _length)
  {
    val = *(double *)(_data + _pntr);

    _pntr += sizeof(double);

    _error = 0;
  }
  else
    _error = ENODATA;
    
  return val;
}

//
// Is the end of the readable reached ?
//
// @return is it?
//
- (BOOL) isEof
{
  return (_pntr >= _length);
}


//// TextScannable protocol methods

//
// Scan for a text string until a separator character. The separator character is 
// is not part of the returned string and is skipped in the Readable.
//
// @return a (new) text string (or nil if the separator is not found)
//
- (DText *) scanText :(char) separator
{
  DText *text = nil;
  long   temp = _pntr;

  while ((temp < _length) && (isprint(_data[temp])) && (_data[temp] != (unsigned char) separator))
  {
    temp++;
  }

  if ((temp < _length) && (_data[temp] == (unsigned char) separator))
  {
    long index;

    text = [DText new];

    for (index = _pntr; index <= temp-1; index++)
    {
      [text push :(char) _data[index]];
    }

    _pntr = temp + 1;
  }

  return text;
}

#if _PRIVATE_
//
// Check if a character is part of a string of separators
//
// @param separators  the separator characters (<> NULL)
//
// @return is a match found ?
//
static BOOL matchSeparator(unsigned char ch, const char *separators)
{
  while (*separators != EOS)
  {
    if (ch == (unsigned char) (*separators))
    {
      return YES;
    }
    
    separators++;
  }
  
  return NO;
}
#endif

//
// Scan for a text string until one of the separator characters in the 
// separator string.  The matched separator character is is not part of 
// the returned string, is skipped in the text object and is returned in 
// the separator argument.
//
// @param separators  the c-string with the separators
// @param separator   the destination for the actual matched separator character (or NULL)
//
// @return a (new) text string (or nil if none of the separators is found)
//
- (DText *) scanText :(const char *) separators :(char *) separator
{
  DText *text = nil;
  
  if (separators == NULL)
  {
    WARNING(DW_INVALID_ARG, "separators");
  }
  else
  {
    long   temp = _pntr;

    while ((temp < _length) && (isprint(_data[temp])) && (!matchSeparator(_data[temp], separators)))
    {
      temp++;
    }

    if ((temp < _length) && (matchSeparator(_data[temp], separators)))
    {
      long index;

      text = [DText new];

      for (index = _pntr; index <= temp-1; index++)
      {
        [text push :(char) _data[index]];
      }

      _pntr = temp + 1;
      
      if (separator != NULL)
      {
        *separator = (char) _data[temp];
      }
    }
  }

  return text;
}
    

//
// Match a string case sensitive; if the string matched, the current position in
// the readable is moved to the location after the matched string.
//
// @param cstring   the c-string to be matched
//
// @return does the string match ?
//
- (BOOL) cmatch :(const char *) cstring
{
  BOOL matched = NO;

  if ((cstring == NULL) || (*cstring == EOS))
  {
    WARNING(DW_INVALID_ARG, "cstring");
  }
  else
  {
    long length = strlen(cstring);
    long temp   = _pntr;
    long index  = 0;

    while ((temp < _length) && (index < length) && (_data[temp] == cstring[index]))
    {
      temp++;
      index++;
    }

    if (index == length)
    {
      matched = YES;
      _pntr   = temp;
    }
  }

  return matched;
}

//
// Match a string case insensitive; if the string matched, the current position in
// the readable is moved to the location after the matched string.
//
// @param cstring   the c-string to be matched
//
// @return does the string match ?
//
- (BOOL) imatch :(const char *) cstring
{
  BOOL matched = NO;

  if ((cstring == NULL) || (*cstring == EOS))
  {
    WARNING(DW_INVALID_ARG, "cstring");
  }
  else
  {
    long length = strlen(cstring);
    long temp   = _pntr;
    long index  = 0;

    while ((temp < _length) && (index < length) && (tolower(_data[temp]) == tolower(cstring[index])))
    {
      temp++;
      index++;
    }

    if (index == length)
    {
      matched = YES;
      _pntr   = temp;
    }
  }

  return matched;
}

//
// Scan for a (decimal) integer value
//
// @param wrong   the value returned if there is no int to scan
//
// @return the integer read (or the wrong value)
//
- (int) scanInt :(int) wrong
{
  int  result = 0;
  long temp   = _pntr;
  BOOL neg    = NO;

  if ((temp < _length) && (_data[temp] == '-'))
  {
    neg = YES;

    temp++;
  }

  while ((temp < _length) && (isdigit(_data[temp])))
  {
    result = result * 10 + (int) (_data[temp] - '0');

    temp++;
  }

  if (temp != _pntr)
  {
    _pntr = temp;

    if (neg) 
      result = -result;
  }
  else
  {
    result = wrong;
  }

  return result;
}

//
// Skip characters
//
// @param ch    the character to be skipped
//
// @return the number of characters skipped
//
- (int) skipChar :(char) ch
{
  int count = 0;

  while ((_pntr < _length) && (_data[_pntr] == (unsigned char) ch))
  {
    _pntr++;
    count++;
  }

  return count;
}

//
// Skip whitespace
//
// @return the number of characters skipped
//
- (int) skipWhiteSpace
{
  int count = 0;

  while ((_pntr < _length) && (isspace(_data[_pntr])))
  {
    _pntr++;
    count++;
  }

  return count;
}


//// DDataWritable protocol methods

//
// Write text string 
//
// @param text     the text to be written
//
// @return success
//
- (BOOL) writeText :(const char *) text
{
  int length = (text != NULL) ? strlen(text) : 0;

  [self writeData :(const unsigned char *) text :length];

  _error = 0;
  
  return YES;
}

//
// Write the data string 
//
// @param data     the data to be written
// @param length   the length of the data
//
// @return success
//
- (BOOL) writeData :(const unsigned char *) text :(unsigned long) length
{
  length = (text != NULL) ? length : 0;
  
  if (_pntr + length > _length)
  {
    _length = _pntr + length;

    [self size :_length];
  }
  memcpy(_data + _pntr, text, length);

  _pntr+= length;

  _error = 0;
  
  return YES;
}

//
// Write the character
//
// @param ch       the character to be written
//
// @return success
//
- (BOOL) writeChar :(char) ch
{
  if (_pntr + sizeof(ch) > _length)
  {
    _length = _pntr + sizeof(ch);

    [self size :_length];
  }
  _data[_pntr++] = (unsigned char) ch;

  _error = 0;
  
  return YES;
}

//
// Write the byte
//
// @param byte     the byte to be written
//
// @return success
//
- (BOOL) writeByte :(unsigned char) byte
{
  if (_pntr + sizeof(byte) > _length)
  {
    _length = _pntr + sizeof(byte);

    [self size :_length];
  }
  _data[_pntr++] = byte;

  _error = 0;
  
  return YES;
}

//
// Write the short
//
// @param sh       the short to be written
//
// @return success
//
- (BOOL) writeShort :(short) sh
{
  if (_pntr + sizeof(sh) > _length)
  {
    _length = _pntr + sizeof(sh);

    [self size :_length];
  }
  *(short *)(_data + _pntr) = sh;

  _pntr += sizeof(sh);

  _error = 0;
  
  return YES;
}

//
// Write the long
//
// @param val      the long to be written
//
// @return success
//
- (BOOL) writeLong :(long) val
{
  if (_pntr + sizeof(val) > _length)
  {
    _length = _pntr + sizeof(val);

    [self size :_length];
  }
  *(long *)(_data + _pntr) = val;

  _pntr += sizeof(val);

  _error = 0;
  
  return YES;      
}

//
// Write the double
//
// @param val      the double to be written
//
// @return the object
//
- (BOOL) writeDouble :(double) val
{
  if (_pntr + sizeof(val) > _length)
  {
    _length = _pntr + sizeof(val);
    
    [self size :_length];
  }
  *(double *)(_data + _pntr) = val;

  _pntr += sizeof(val);

  _error = 0;
  
  return YES;
}

//
// Flush the output buffers
//
// @return success
//
- (BOOL) flush
{
  return YES;
}


//// DDataReadable and DDataWritable protocol methods

//
// Tell the current position in the writeable
//
// @return the current position
//
- (unsigned long) tell
{
  _error = 0;
  
  return _pntr;
}

//
// Move the current position to a location
//
// @param offset   the offset from the origin (in bytes)
// @param origin   the origin for the offset (0=begin, 1=current, 2=end,others = no change)
//
// @return the object
//
- (BOOL) seek :(unsigned long) offset :(int) origin
{
  BOOL ok = YES;
  
  switch (origin)
  {
    case 0:
      if (offset > _length)
      {
        _pntr = _length;
        ok    = NO;
      }
      else
        _pntr = offset;
      break;
      
    case 1:
      if (_pntr + offset > _length)
      {
        _pntr = _length;
        ok    = NO;
      }
      else
        _pntr += offset;
      break;
      
    case 2:
      if (_length - offset < 0)
      {
        _pntr = 0;
        ok    = NO;
      }
      else
        _pntr = _length - offset;
      break;

    default:
      WARNING(DW_INVALID_ARG, "origin");
      break;
  }

  _error = (ok ? 0 : ERANGE);
  
  return ok;
}

//
// Skip a number of positions
//
// @param offset   the number of bytes to skip
//
// @return success
//
- (BOOL) skip :(unsigned long) offset
{
  if (_pntr + offset > _length)
  {
    _pntr  = _length;
    _error = ERANGE;

    return NO;
  }
  else
  {
    _pntr += offset;
    _error = 0;

    return 0;
  }
}


//// Data string manipulation methods

//
// Append data to the data string
//
// @param data     the data to be appended
// @param length   the length of the data to be appended
//
// @return the object
//
- (DData *) append :(const unsigned char *) data :(unsigned long) length
{
  length = (data != NULL) ? length : 0;

  if (length > 0)
  {
    [self size :(_length + length)];

    memcpy(_data + _length, data, length);
    
    _length += length;
  }
  
  return self;
}

//
// Prepend data in front of the string
//
// @param data     the data to be prepended
// @param length   the length of the data to be prepended
//
// @return the object
//
- (DData *) prepend :(const unsigned char *) data :(unsigned long) length
{
  length = (data != NULL) ? length : 0;

  if (length > 0)
  {
    [self size :(_length + length)];

    memmove(_data + length, _data, _length);
    
    memcpy(_data, data, length);

    _length += length;
  }

  return self;
}

//
// Push one byte at the end of the data string
// 
// @param ch       the byte that is placed at the end
//
// @return the object
//
- (DData *) push :(unsigned char) ch
{
  [self size :(_length + 1)];

  _data[_length++] = ch;

  return self;
}

//
// Pop one byte from the end of the data string
//
// @return the byte that is pulled (or 0)
//
- (unsigned char) pop
{
  unsigned char ch = 0;
  
  if (_length > 0)
  {
    ch = _data[--_length];
  }

  return ch;
}

//
// Multiply the data string a number of times
//
// @param times    the number of times the data string must be multiplied
//
// @return the object
//
- (DData *) multiply :(unsigned) times
{
  long length = _length;
  long dst    = _length;
  
  if (times > 0)
  {
    _length *= times;

    if (_length > 0)
    {
      [self size :_length];

      while (times > 1)
      {
        memcpy(_data + dst, _data, length);

        dst += length;
      
        times--;
      }
    }
  }
  else
    WARNING(DW_INVALID_ARG, "times");
  
  return self;
}


//// Comparable protocol methods

//
// Compare the data string object with another data string object
//
// @param other    the object that should be compared (!= nil)
//
// @return the compare result (-1, 0, 1)
//
- (int) compare :(DData *) other
{
  unsigned char *c1       = _data;
  long l1                 = _length;
  const unsigned char *c2 = [other data];
  long l2                 = [other size];
  
  while ((l1 > 0) && (l2 > 0) && (*c1 == *c2))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (*c1 > *c2) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}


//// Comparason methods

//
// Compare the object with a data string
//
// @param data     the data string
// @param length   the length of the data string
//
// @return the compare result (-1, 0, 1)
//
- (int) bcompare :(unsigned char *) data :(unsigned long) length
{
  unsigned char *c1       = _data;
  long l1                 = _length;
  const unsigned char *c2 = data;
  long l2                 = (data != NULL) ? length : 0;
  
  while ((l1 > 0) && (l2 > 0) && (*c1 == *c2))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (*c1 > *c2) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}


//// Search and replace methods

//
// Count the number of occurrences of data in the data string
//
// @param search   the data that should be counted
// @param length   the length of the data to be counted
// @param from     the start index 
// @param to       the end index (to >= from)
//
// @return the number of occurrences
//
- (unsigned long) count :(const unsigned char *) search :(unsigned long) length :(long) from :(long) to
{
  unsigned long cnt = 0;
  
  if (search != NULL)
  {
    from = index2offset(self, from);
    to   = index2offset(self ,to);

    while (from <= to)
    {
      if (memcmp(_data + from, search, length) == 0)
      {
        cnt++;

        from += length;
      }
      else
      {
        from++;
      }
    }
  }
      
  return cnt;
}

//
// Return the first index where the search data is found
//
// @param search   the data that should be searched
// @param length   the length of the data
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return the first index or -1 = not found
//
- (long) index  :(const unsigned char *) search :(unsigned long) length :(long) from :(long) to
{
  if (search != NULL)
  { 
    from = index2offset(self, from);
    to   = index2offset(self, to  );
    
    to -= length;
    
    while (from <= to)
    {
      if (memcmp(_data + from, search, length) == 0)
      {
        return from;
      }
      from++;
    }
  }

  return -1;
}

//
// Return the last index where the search data is found
//
// @param search   the data that should be searched
// @param length   the length of the data
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return the last index or -1 = not found
//
- (long) rindex :(const unsigned char *) search :(unsigned long) length :(long) from :(long) to
{
  if (search != NULL)
  {
    from = index2offset(self, from);
    to   = index2offset(self, to  );

    to -= length;

    while (to >= from)
    {
      if (memcmp(_data + to, search, length) == 0)
      {
        return to;
      }
      to--;
    }
  }
  
  return -1;
}

//
// Replace in the string the occurrences of old with new, max times
//
// @param old      the data that is searched
// @param olength  the length of the searched data
// @param new      the data that is inserted
// @param nlength  the length of the inserted data
// @param max      the maximum number of replaces (-1 = all)
//
// @return the object
//
- (DData *) replace :(const unsigned char *) old :(unsigned long) olength 
                    :(const unsigned char *) new :(unsigned long) nlength :(long) max
{
  if ((old != NULL) && (_length > 0))
  {
    long cnt       = 0;
    long delta     = 0;
    char *copy     = (char *) objc_malloc(_length);
    char *src      = copy;
    long  len      = _length;
    long  dst      = 0;
    
    nlength = (new != NULL) ? nlength : 0;
    delta   = nlength - olength;
    
    memcpy(copy, _data, _length);

    while (len > 0) 
    {
      if (((cnt < max) || (max == -1)) && (memcmp(src, old, olength) == 0))
      {
        cnt++;
        
        _length += delta;
        if (_length >= _size)
        {
          [self size :(_length + 16 * delta)];
        }

        if (nlength > 0)
        {
          memcpy(_data + dst, new, nlength);

          dst += nlength;
        }
        src += olength;
        
        len -= olength;
      }
      else
      {
        _data[dst++] = *src++;

        len--;
      }
    }

    objc_free(copy);
  }
  
  return self;
}

@end

/*===========================================================================*/

